射频识别(RFID)技术

读写操作

译者:陈广 日期:2020-6-21


在上一篇文章中,我们已经配置好开发环境以及外围代码,本文讲解 ST25DV64K 的读写操作。首先我们要了解 ST25DV64K 存储器的结构。

存储器管理

ST25DV64K 的存储器被划分为如下图所示的四个部分:

  • 用户存储器
  • 动态寄存器
  • 快速传输模式缓冲区
  • 系统配置区

图 1:存储结构
  1. 用户存储器容量我们在上篇文章中已经读取了出来,为 8192 字节,用于给用户存放数据,本文所讲的读写操作就是在此区域上进行的。此存储器最多可被划分为 4 个区域,分区的最大意义在于可以给每个区以不同的权限,比如通过设置密码,可指定某些区域仅对管理者开放,而某些区域则对用户开放。芯片在出厂时默认只有一个区,数据全部为 0x00。
  2. 动态寄存器是静态寄存器的映射,它的意义在于可以临时更改芯片行为以方便进行某些操作,而在重启后又恢复为静态寄存器的设置。
  3. 快速传输模式缓冲区的容量为 256 字节,通过它可以将手机上的数据直接传送给单片机,这真是一个让人激动的强大功能。
  4. 系统配置区存放了密码、UID等信息。

我这里只是领大家入个门,更详细的信息请参考《ST25DV64K 中文手册》。

读写操作

读写操作比较简单,因为 STM 已经帮我们包装好了。在【custom_nfc04a1_nfctag.c】文件中,找两个函数:CUSTOM_NFCTAG_ReadDataCUSTOM_NFCTAG_WriteData

读操作原型为:

int32_t CUSTOM_NFCTAG_ReadData( uint32_t Instance, uint8_t * const pData, const uint16_t TarAddr, const uint16_t Size )

其中,pData是用于存放读出来的数据;TarAddr表示读取的起始地址;Size表示读多少个字节的数据。

写操作原型为:

int32_t CUSTOM_NFCTAG_WriteData( uint32_t Instance, const uint8_t * const pData, const uint16_t TarAddr, const uint16_t Size )

其中,pData为写缓冲,我们将要写入的数据存放在这里;TarAddr表示写入的起始地址;Size表示写多少个字节的数据。

代码编写

下面我们编写一个程序,每次向 ST25DV64K 的 EEDPROM 写入 10 个字节的数据,然后将这 10 个数据读出来。反复读写,从 0~255。将上篇文章中main()函数参照以下代码进行更改:

/* USER CODE BEGIN 2 */
//如果ST25DV64K初始化成功,则点亮 LED
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
uint8_t writeData[10];
uint8_t readData[10];
int index=0;
HAL_Delay(5000);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (index<256)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
    //写入数据
    int i;
    for(i=0;i<10;i++) //将10字节数据压入缓冲
    {
        writeData[i]=i+index;
    }
    //写入 NFC 标签
    if(CUSTOM_NFCTAG_WriteData(CUSTOM_NFCTAG_INSTANCE, writeData, index, 10)==NFCTAG_OK)
    {
        printf("Write Data Success! Start = %d\r\n", index);
    }
    else
    {
        printf("Write Data Error! Start = %d\r\n", index);
    }

    //读取数据
    if(CUSTOM_NFCTAG_ReadData(CUSTOM_NFCTAG_INSTANCE, readData, index, 10)==NFCTAG_OK)
    {
        int i;
        printf("ADDR%d = ",index);
        for(i=0;i<10;i++)
        {
            printf("%02X ",readData[i]);
        }
    }
    else
    {
        printf("Read Error! Start = %d\r\n", index);
    }
    printf("\r\n");
    index+=10;
    HAL_Delay(2000);
}
/* USER CODE END 3 */

运行结果如下图所示:

图 2:程序运行效果

对存储器进行分区及设置权限操作

掌握基本的读写操作后,我们来对存储器进行分区。新买回来的芯片只有一个分区,我们现在将其划分为相等的四个部分,并将每个分区的读写权限进行不同的设置,以方便后面的实验。

参照以下代码对main()函数进行更改:

/* USER CODE BEGIN 2 */
//如果ST25DV64K初始化成功,则点亮 LED
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(5000); //延时 5 秒,以留给用户连接上位机的时间

//在更改存储器结构前需要进行一些设置
CUSTOM_NFCTAG_ResetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE);
ST25DV_I2CSSO_STATUS i2csso;
CUSTOM_NFCTAG_ReadI2CSecuritySession_Dyn(CUSTOM_NFCTAG_INSTANCE,&i2csso);
if(i2csso == ST25DV_SESSION_CLOSED)
{ //划分及设置存储器前需要进行密码验证
    ST25DV_PASSWD pwd;
    pwd.MsbPasswd = 0;
    pwd.LsbPasswd = 0;
    CUSTOM_NFCTAG_PresentI2CPassword(CUSTOM_NFCTAG_INSTANCE,pwd);
    printf("authentication done!\r\n");
}
/*将存储器划分四个相等区域,每个区域大小为2048字节
            _______________
       @0000|               |
            |               |
            |     Zone 1    |
            |               |
       @2048|---------------|
            |               |
            |     Zone 2    |
            |               |
       @4096|---------------|
            |               |
            |     Zone 3    |
            |               |
       @6144|---------------|
            |               |
            |     Zone 4    |
            |               |
       @8192|_______________|
*/
//将存储器划分为四个大小相等的区
CUSTOM_NFCTAG_CreateUserZone(CUSTOM_NFCTAG_INSTANCE,2048,2048,2048,2048);
printf("Create User Zone done\r\n");
HAL_Delay(2000);

//将四个区域分别设置为不同的权限
int32_t ret;
//将第一个区域设置为无保护
ret = CUSTOM_NFCTAG_WriteI2CProtectZonex(CUSTOM_NFCTAG_INSTANCE,ST25DV_PROT_ZONE1,ST25DV_NO_PROT);
if(ret == NFCTAG_OK)
{
    printf("Zone 1 set no protect success!\r\n");
}
else
{
    printf("Zone 1 set no protect failed!\r\n");
}
HAL_Delay(2000);
//将第二个区域设置为写保护(即写入需要密码)
ret = CUSTOM_NFCTAG_WriteI2CProtectZonex(CUSTOM_NFCTAG_INSTANCE,ST25DV_PROT_ZONE2,ST25DV_WRITE_PROT);
if(ret == NFCTAG_OK)
{
    printf("Zone 2 set write protect success!\r\n");
}
else
{
    printf("Zone 2 set write protect failed!\r\n");
}
HAL_Delay(2000);
//将第三个区域设置为读保护(即读取需要密码)
ret = CUSTOM_NFCTAG_WriteI2CProtectZonex(CUSTOM_NFCTAG_INSTANCE,ST25DV_PROT_ZONE3,ST25DV_READ_PROT);
if(ret == NFCTAG_OK)
{
    printf("Zone 3 set read protect success!\r\n");
}
else
{
    printf("Zone 3 set read protect failed!\r\n");
}
HAL_Delay(2000);
//将第四个区域设置为读写保护(读写都需要密码)
ret = CUSTOM_NFCTAG_WriteI2CProtectZonex(CUSTOM_NFCTAG_INSTANCE,ST25DV_PROT_ZONE4,ST25DV_READWRITE_PROT);
if(ret == NFCTAG_OK)
{
    printf("Zone 4 set read/write protect success!\r\n");
}
else
{
    printf("Zone 4 set read/write protect failed!\r\n");
}

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

运行结果:

authentication done!
Create User Zone done
Zone 1 set no protect success!
Zone 2 set write protect success!
Zone 3 set read protect success!
Zone 4 set read/write protect success!

需要注意的是,芯片的出厂密码是全0。另外,比较诡异的是,经过实验,我发现CUSTOM_NFCTAG_PresentI2CPasswordCUSTOM_NFCTAG_CreateUserZone方法即使失败,其返回值还是NFCTAG_OK,没办法通过返回值来判定这两个方法是否成功执行,所以在程序中我并未判断其返回值。

验证权限

下面以不同的验证方式进行读写操作,以观察对四个区的操作有何不同。

不进行密码验证

首先不进行密码验证而分别对四个区进行读写操作,将main()函数代码更改如下:

/* USER CODE BEGIN 2 */
//如果ST25DV64K初始化成功,则点亮 LED
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(5000); //延时 5 秒,以留给用户连接上位机的时间

/*将存储器划分四个相等区域,每个区域大小为2048字节
             _______________
       @0000|               |
            |               |
            |     Zone 1    |
            |   no protect  |
       @2048|---------------|
            |               |
            |     Zone 2    |
            | write protect |
       @4096|---------------|
            |               |
            |     Zone 3    |
            | read protect  |
       @6144|---------------|
            |               |
            |     Zone 4    |
            | read write protect
       @8192|_______________|
*/

/* USER CODE END 2 */
uint8_t writeData[10]={1,2,3,4,5,6,7,8,9,10};
uint8_t readData[10];
//读写第一区数据
//写入 NFC 标签
if(CUSTOM_NFCTAG_WriteData(CUSTOM_NFCTAG_INSTANCE, writeData, 0, 10)==NFCTAG_OK)
{
    printf("Write Data Success!\r\n");
}
else
{
    printf("Write Data Error!\r\n");
}
HAL_Delay(1000);
//读取数据
if(CUSTOM_NFCTAG_ReadData(CUSTOM_NFCTAG_INSTANCE, readData, 0, 10)==NFCTAG_OK)
{
    int i;
    printf("ADDR 0 = ");
    for(i=0;i<10;i++)
    {
        printf("%02X ",readData[i]);
    }
    printf("\r\n");
}
else
{
    printf("ADDR 0 read error!\r\n");
}
HAL_Delay(1000);

//读写第二区数据
//写入 NFC 标签
if(CUSTOM_NFCTAG_WriteData(CUSTOM_NFCTAG_INSTANCE, writeData, 2048, 10)==NFCTAG_OK)
{
    printf("Write Data Success!\r\n");
}
else
{
    printf("Write Data Error!\r\n");
}
HAL_Delay(1000);
//读取数据
if(CUSTOM_NFCTAG_ReadData(CUSTOM_NFCTAG_INSTANCE, readData, 2048, 10)==NFCTAG_OK)
{
    int i;
    printf("ADDR 2048 = ");
    for(i=0;i<10;i++)
    {
            printf("%02X ",readData[i]);
    }
    printf("\r\n");
}
else
{
    printf("ADDR 2048 read error!\r\n");
}
HAL_Delay(1000);

//读写第三区数据
//写入 NFC 标签
if(CUSTOM_NFCTAG_WriteData(CUSTOM_NFCTAG_INSTANCE, writeData, 4096, 10)==NFCTAG_OK)
{
    printf("Write Data Success!\r\n");
}
else
{
    printf("Write Data Error!\r\n");
}
HAL_Delay(1000);
//读取数据
if(CUSTOM_NFCTAG_ReadData(CUSTOM_NFCTAG_INSTANCE, readData, 4096, 10)==NFCTAG_OK)
{
    int i;
    printf("ADDR 4096 = ");
    for(i=0;i<10;i++)
    {
            printf("%02X ",readData[i]);
    }
    printf("\r\n");
}
else
{
    printf("ADDR 4096 read error!\r\n");
}
HAL_Delay(1000);

//读写第四区数据
//写入 NFC 标签
if(CUSTOM_NFCTAG_WriteData(CUSTOM_NFCTAG_INSTANCE, writeData, 6144, 10)==NFCTAG_OK)
{
    printf("Write Data Success!\r\n");
}
else
{
    printf("Write Data Error!\r\n");
}
HAL_Delay(1000);
//读取数据
if(CUSTOM_NFCTAG_ReadData(CUSTOM_NFCTAG_INSTANCE, readData, 6144, 10)==NFCTAG_OK)
{
    int i;
    printf("ADDR 6144 = ");
    for(i=0;i<10;i++)
    {
            printf("%02X ",readData[i]);
    }
    printf("\r\n");
}
else
{
    printf("ADDR 6144 read error!");
}
HAL_Delay(1000);
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

运行结果:

Write Data Success!
ADDR 0 = 01 02 03 04 05 06 07 08 09 0A 
Write Data Error!
ADDR 2048 = 00 00 00 00 00 00 00 00 00 00 
Write Data Success!
ADDR 4096 = FF FF FF FF FF FF FF FF FF FF 
Write Data Error!
ADDR 6144 = FF FF FF FF FF FF FF FF FF FF 

可以看到,读错误也返回NFCTAG_OK,但返回的全是0xFF。此次读写结果完全符合每个区所对应的保护状态。

输入错误的密码

不改变之前的代码,在

//如果ST25DV64K初始化成功,则点亮 LED
while( CUSTOM_NFCTAG_Init(CUSTOM_NFCTAG_INSTANCE) != NFCTAG_OK );
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(5000); //延时 5 秒,以留给用户连接上位机的时间

后方添加如下代码:

CUSTOM_NFCTAG_ResetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE);
ST25DV_I2CSSO_STATUS i2csso;
CUSTOM_NFCTAG_ReadI2CSecuritySession_Dyn(CUSTOM_NFCTAG_INSTANCE,&i2csso);
if(i2csso == ST25DV_SESSION_CLOSED)
{ //划分及设置存储器前需要进行密码验证
    ST25DV_PASSWD pwd;
    pwd.MsbPasswd = 1;
    pwd.LsbPasswd = 2;
    CUSTOM_NFCTAG_PresentI2CPassword(CUSTOM_NFCTAG_INSTANCE,pwd);
    printf("authentication done!\r\n");
}

运行结果:

authentication done!
Write Data Success!
ADDR 0 = 01 02 03 04 05 06 07 08 09 0A 
Write Data Error!
ADDR 2048 = 00 00 00 00 00 00 00 00 00 00 
Write Data Success!
ADDR 4096 = FF FF FF FF FF FF FF FF FF FF 
Write Data Error!
ADDR 6144 = FF FF FF FF FF FF FF FF FF FF 

可以看到结果跟不进行密码验证完全一样,逻辑上说也应该一样,这个实验白做。

输入正确的密码

将刚才的代码更改如下:

CUSTOM_NFCTAG_ResetMBEN_Dyn(CUSTOM_NFCTAG_INSTANCE);
ST25DV_I2CSSO_STATUS i2csso;
CUSTOM_NFCTAG_ReadI2CSecuritySession_Dyn(CUSTOM_NFCTAG_INSTANCE,&i2csso);
if(i2csso == ST25DV_SESSION_CLOSED)
{ //划分及设置存储器前需要进行密码验证
    ST25DV_PASSWD pwd;
    pwd.MsbPasswd = 0;
    pwd.LsbPasswd = 0;
    CUSTOM_NFCTAG_PresentI2CPassword(CUSTOM_NFCTAG_INSTANCE,pwd);
    printf("authentication done!\r\n");
}

其实就是将密码改为 0,运行结果如下:

authentication done!
Write Data Success!
ADDR 0 = 01 02 03 04 05 06 07 08 09 0A 
Write Data Success!
ADDR 2048 = 01 02 03 04 05 06 07 08 09 0A 
Write Data Success!
ADDR 4096 = 01 02 03 04 05 06 07 08 09 0A 
Write Data Success!
ADDR 6144 = 01 02 03 04 05 06 07 08 09 0A 

密码正确后,所有操作均成功运行。

ST25DV64K的基本操作也就这些了,下面要转战 Android 了,之前弄的安卓现在也差不多忘完了,又得重来一次,太痛苦了!

;

© 2018 - IOT小分队文章发布系统 v0.3